
#include "mlisp.h"
#include "sexp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "mffi.h"

#ifndef	FALSE
#define	FALSE	(0)
#define	TRUE	(1)
#endif

#define	COMPILER_PATH	"compiler.l"

void usage() {
	printf(
		"usage:\n"
		"  -c     compile only\n"
		"  -r     raw code\n"
		"  -e     one liner\n"
		"  -w     write result\n"
	);
}

typedef struct {
	int compile_only;
	int raw_code;
	const char* exp;
	int write_result;
} Option;

void init_opt(Option* opt) {
	opt->compile_only = FALSE;
	opt->raw_code = FALSE;
	opt->exp = NULL;
	opt->write_result = FALSE;
}

int parse_opt(Option* opt, int argc, char* argv[]) {
	int i;

	for (i=1; i<argc; ++i) {
		if (argv[i][0] != '-') {
			break;
		}

		switch (argv[i][1]) {
		default:	fprintf(stderr, "Unknown option, try -h");	break;
		case 'h':	usage();	break;
		case 'c':	opt->compile_only = TRUE;	break;
		case 'r':	opt->raw_code = TRUE;	break;
		case 'e':	opt->exp = argv[++i];	break;
		case 'w':	opt->write_result = TRUE;	break;
		}
	}
	return i;
}

static void repl(Option* opt) {
	SExp eval;
	if (!opt->raw_code) {
		eval = refer_global(intern("eval"));
	}
	for (;;) {
		SExp x;
		printf("> ");
		x = sread(sstdin);
		if (eq(x, sEOF)) {
			break;
		}
		if (opt->raw_code) {
			run(x, 0);
		} else {
			SExp r = apply(eval, 0, 1, &x);
			swrite(r, TRUE, sstdout);
			fputs("\n", stdout);
		}
	}
}

static SExp do_stream(StreamBase* strm) {
	SExp eval = refer_global(intern("eval"));
	SExp r = sUNDEF;
	for (;;) {
		SExp code = sread(strm);
		if (eq(code, sEOF)) {
			break;
		}
		r = apply(eval, 0, 1, &code);
	}
	return r;
}

static SExp dostring(const char* exps) {
	StringStream* ss = (StringStream*)smalloc(sizeof(*ss), FALSE);
	StreamBase* strm = make_string_stream(ss, exps, strlen(exps));
	SExp r = do_stream(strm);
	close_stream(strm);
	return r;
}

static SExp dofile(const char* fn) {
	FILE* fp = fopen(fn, "r");
	if (fp == NULL) {
		fprintf(stderr, "Can't open: %s\n", fn);
		return sUNDEF;
	} else {
		FileStream fs;
		StreamBase* strm = make_file_stream(&fs, fp);
		SExp r = do_stream(strm);
		close_stream(strm);
		fclose(fp);
		return r;
	}
}

static SExp call_main(int n, char* const v[]) {
	SExp main = refer_global(intern("main"));
	if (!eq(main, sUNDEF)) {
		SExp args = sNIL;
		int i;
		for (i = n; --i >= 0; ) {
			const char* s = v[i];
			args = cons(gen_string(s, strlen(s)), args);
		}
		return apply(main, 0, 1, &args);
	} else {
		return sFALSE;
	}
}

static void compile_stream(StreamBase* strm) {
	SExp comp = refer_global(intern("comp"));
	SExp buf[2];
	buf[1] = fixnum2s(0);
	for (;;) {
		SExp code = sread(strm);
		SExp r;
		if (eq(code, sEOF)) {
			break;
		}

		buf[0] = code;
		r = apply(comp, 0, 2, buf);
		swrite(r, TRUE, sstdout);
		fputs("\n", stdout);
	}
}

static void compile_string(const char* exps) {
	StringStream* ss = (StringStream*)smalloc(sizeof(*ss), FALSE);
	StreamBase* strm = make_string_stream(ss, exps, strlen(exps));
	compile_stream(strm);
	close_stream(strm);
}

static int compile_file(const char* fn) {
	FILE* fp = fopen(fn, "r");
	if (fp == NULL) {
		return FALSE;
	} else {
		FileStream fs;
		StreamBase* strm = make_file_stream(&fs, fp);
		compile_stream(strm);
		close_stream(strm);
		fclose(fp);
		return TRUE;
	}
}

int main(int argc, char* argv[]) {
	Option opt;
	int i;

	init_opt(&opt);
	i = parse_opt(&opt, argc, argv);
	init_mlisp();
	install_mffi();

	if (!opt.raw_code) {
		load_fasl(COMPILER_PATH);
	}

	if (opt.exp != NULL) {
		if (opt.compile_only) {
			compile_string(opt.exp);
		} else {
			SExp r = dostring(opt.exp);
			if (opt.write_result) {
				swrite(r, TRUE, sstdout);
				fputs("\n", stdout);
			}
		}
	} else if (i >= argc) {
		repl(&opt);
	} else {
		const char* fn = argv[i];
		if (opt.compile_only) {
			compile_file(fn);
		} else {
			if (!opt.raw_code) {
				dofile(fn);
			} else {
				load_fasl(fn);
			}
			call_main(argc-i-1, &argv[i+1]);
		}
	}

//	dump_symbol_table_info();
	term_mlisp();

	return 0;
}
